/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Communication;

import Common.*;
import java.net.*;
import java.io.*;
import Request.*;
import Response.*;
import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.util.*;
/**
 *
 * @author lcy
 */
public class P2PSockThread extends Thread{
    
    DatagramSocket _main_socket;
    private InfoLogger _debugger; 
    private ByteArrayInputStream _byteStream;
    private ObjectInputStream _is;
    private byte[] _recvBuf;
    private int time_out;

    //Now create a socketchannel for non-blocking io
    private DatagramChannel _main_sock_channel;
    private DatagramSocket _channel_sock;
    private Selector selector = null;
    private ByteArrayInputStream _in2;

    private RequestRdyUtil _request_rdy_util;

    public P2PSockThread(){

	InitCommComponents();
    }

    private void InitCommComponents(){

	_debugger = new InfoLogger();
        time_out = 0;
        _request_rdy_util = new RequestRdyUtil();
    }

    @Override
    public void run(){
        try{
	    _main_socket = new DatagramSocket(CONSTANTS.P2P_BLOCK_PORT);
            _main_socket.setSoTimeout(time_out);
	    
	    //The channel stuff
	    _main_sock_channel = DatagramChannel.open();
	    _main_sock_channel.configureBlocking(false);
	    _channel_sock = _main_sock_channel.socket();
	    _channel_sock.bind(new InetSocketAddress("localhost",CONSTANTS.UDP_P2P_IN_PORT));
            
	    while(!_main_sock_channel.isConnected()){
		_main_sock_channel.connect(new 
                        InetSocketAddress("localhost",CONSTANTS.UDP_P2P_NON_BLK_PORT));
	    }

            _debugger.LogInfo("P2P socket connected.");
	    _channel_sock = _main_sock_channel.socket();
	    selector = Selector.open();
	    _main_sock_channel.register(selector,_main_sock_channel.validOps());
	    //End of channel stuff;

	}catch(SocketException ex){
	    //ex.printStackTrace();
	}catch(IOException e){
            //Do nothing?
        }

        //First open all socket channel for blocking receive
	_recvBuf = new byte [CONSTANTS.UDP_P2P_BUF_SIZE];
	_byteStream = new ByteArrayInputStream(_recvBuf);
        
        while(true){
            Request request = null;
	    try{
		selector.select();
	    }catch(IOException e){
		break;
	    }

	    Iterator it = selector.selectedKeys().iterator();

	    while(it.hasNext()){
		SelectionKey selKey = (SelectionKey)it.next();
		it.remove();

		try{
		    request = ProcessTheKey(selKey);
                    
                    if(request != null){
                       NetDataProcessor processor = new NetDataProcessor(request);
                       NetDataResponse response = 
                               NetDataResponse.CreateResponse(request);
                       processor.FormResponse(response);
                       RequestRdyArg arg = new RequestRdyArg(response);
                       arg.SetRequestId(this.getId()); // Fill in request id;

                       //Fire the event;
                       _request_rdy_util._fireRequestReadyEvent(arg);
                    }
                    
		}catch(IOException ex1){
		    selKey.cancel();
		}
	    }
	    
            
            //String request = BlockRx();

            //if(request == null)
            //    continue;

            //NetDataProcessor processor = new NetDataProcessor(request);
            
        }

    }

    private Request BlockRx(){

            Request o = null;
	    DatagramPacket packet = new DatagramPacket(_recvBuf,_recvBuf.length);

            try{

                _debugger.LogInfo("Receiving other packets...");
                _main_socket.receive(packet);
                _debugger.LogInfo("Receiving finished.");

                _is = new ObjectInputStream(new BufferedInputStream(_byteStream));

		//The blocking read is here;
                o = (Request) _is.readObject();
                _is.close();
                _debugger.LogInfo("Block Receiving content : " + o);
                _byteStream.reset();
	    }catch(SocketTimeoutException ex){
                //_debugger.LogInfo("P2P: time out");
                
		if(time_out > 10){
                    time_out /= 2;
                }

                try{
                    _main_socket.setSoTimeout(time_out);
                }catch(SocketException ex2){
                    ex2.printStackTrace();
                }
                
                return null;               
            }
            catch(IOException ex){
		//Do nothing;
                return null;
	    }catch(ClassNotFoundException e){
		// Do nothing
                return null;
	    }
           
            time_out *= 2;
            try{
                _main_socket.setSoTimeout(time_out);
            }catch(SocketException ex2){
                ex2.printStackTrace();
            }
            

            return o;
    }

    public void SendPacket(NetDataResponse resp){
        
    }

    public void SendPacket(Request req, int bogus_var){
        
    }

    public Request ProcessTheKey(SelectionKey selKey) throws IOException{
        Request result = null;
        
	if(selKey.isValid() && selKey.isReadable()){
	    DatagramChannel sChannel = (DatagramChannel)selKey.channel();
	    
	    ByteBuffer buf = ByteBuffer.allocateDirect(CONSTANTS.UDP_P2P_BUF_SIZE);
            buf.clear();
            int numBytesRead = sChannel.read(buf);
            
            byte [] transfer_buf = new byte[buf.limit()];

            if(numBytesRead == -1){
                sChannel.close();
                return null;
            }else{
                buf.flip();
                TransferContent(transfer_buf,buf);
            }

            _in2 = new ByteArrayInputStream(transfer_buf);
            ObjectInputStream in_sock = new ObjectInputStream(
                    new BufferedInputStream(_in2));

            try{
                result = (Request) in_sock.readObject();
                _debugger.LogInfo("Datagram Channel: " + result);
		_in2.reset();
            }catch(ClassNotFoundException ex){
                _debugger.LogInfo("Datagram Channel: class not found in obj read");
            }

            in_sock.close();
            _in2.close();
	}
        else if (!selKey.isValid()){
            throw new IOException("Key not valid");
        }
        else if (!selKey.isReadable()){
            throw new IOException("Key not readable");
        }

        return result;
    }

    private void TransferContent(byte [] result, ByteBuffer buf){

        for(int i = 0; i < buf.limit();i ++){
            result[i] = buf.get();
        }
    }

    public synchronized void addRequestReadyHandler( RequestReadyHandler l ) {
        _request_rdy_util.addRequestReadyHandler(l);
    }

    public synchronized void removeRequestReadyHandler( RequestReadyHandler l ) {
        _request_rdy_util.removeRequestReadyHandler(l);
    }
}
